Conversation
탈퇴 후 30일이 지난 사용자를 삭제하는 스케줄러가 외래키 제약에 걸려 매일 롤백되고 있었다. 삭제 목록에서 빠져 있던 데이터를 채우고 순서를 바로잡는다. - 1:1 채팅방은 참여자가 사라지면 유지될 수 없으므로 방 전체를 삭제한다 - 채팅방이 멘토링을 참조하므로 멘토링보다 먼저 삭제한다 - 멘토로 참여한 멘토링과 탈퇴자가 쓴 뉴스의 좋아요를 함께 삭제한다 - 신고로 가려진 게시글과 메시지는 조회 필터에 걸리므로 플래그를 되돌린 뒤 삭제한다 - 정지 기록은 대상 행만 삭제하고, 집행자 참조는 해제해 다른 사용자의 이력을 보존한다 - 프로필 이미지 key 가 비어 있으면 S3 호출을 건너뛴다 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (13)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. Walkthrough탈퇴 사용자 삭제 흐름이 연관 데이터 유형별 헬퍼 메서드를 사용하도록 변경되었습니다. 뉴스 좋아요, 신고로 표시된 게시물, 1:1 채팅 데이터, 멘토링, 제재 참조를 삭제 순서에 맞게 정리합니다. Priority: ⬆️ High Estimated code review effort: 4 (Complex) | ~45 minutes
Merge Risk: ⚪ Minimal · up to The scheduler now removes dependent data before deleting departed users and safely skips empty profile-image keys. No concrete merge-blocking correctness or availability risk is evidenced. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 17.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 12 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a430c8465
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| private Long bannedUserId; | ||
|
|
||
| @Column(name = "banned_by", nullable = false) | ||
| @Column(name = "banned_by") |
There was a problem hiding this comment.
Declare the nullable mapping explicitly
Because this change intentionally makes banned_by nullable, declare that contract as @Column(name = "banned_by", nullable = true) rather than relying on the annotation default. The repository convention requires entity columns to state nullability explicitly, so the current mapping no longer documents the schema change consistently.
AGENTS.md reference: AGENTS.md:L301-L305
Useful? React with 👍 / 👎.
| return; | ||
| } | ||
| chatMessageRepository.unmarkDeletedByChatRoomIdIn(chatRoomIds); | ||
| chatMessageRepository.deleteAllByChatRoomIdIn(chatRoomIds); |
There was a problem hiding this comment.
Delete chat attachment objects before dropping their rows
When a deleted room contains files uploaded through the chat upload endpoint, this call cascades removal of the ChatAttachment database rows but never deletes the objects referenced by ChatAttachment.url from S3. Those keys become unreachable after the rows are removed and accumulate permanently; collect and delete the attachment objects before deleting the messages.
Useful? React with 👍 / 👎.
| * - 채팅방이 멘토링을 참조하므로 멘토링보다 먼저 삭제한다. | ||
| * */ | ||
| private void deleteChatRooms(long siteUserId) { | ||
| List<Long> chatRoomIds = chatParticipantRepository.findAllChatRoomIdsBySiteUserId(siteUserId); |
There was a problem hiding this comment.
현재 ChatRoom의 스키마 상 isGroup 컬럼이 존재합니다! 하지만 현재 findAllChatRoomIdsBySiteUserId가 isGroup 여부를 구분하지 않아서, 탈퇴자가 속한 채팅방은 1:1이든 그룹이든 전부 여기서 삭제 대상이 됩니다.
PR 설명에는 "1:1 채팅방은 참여자가 사라지면 유지될 수 없으므로 삭제"라고 되어 있는데 코드에는 그 조건이 명시돼 있지 않네요. 지금은 그룹 채팅방을 생성하는 경로가 없어 실제 영향은 없지만, ChatRoom.isGroup이 이미 존재하고 ChatService에서도 분기 처리하는 걸 보면 나중에 그룹 채팅에 대해서도 대비가 되게 구현하는 게 좋아보입니다.
그때 멤버 한 명 탈퇴로 다른 멤버들의 방 전체가 사라지는 회귀를 막기 위해, isGroup = false인 방만 대상으로 삼도록 조건을 걸거나 최소한 의도를 주석/TODO로 남겨두면 좋을 것 같습니다!
| chatMessageRepository.deleteAllByChatRoomIdIn(chatRoomIds); | ||
| chatReadStatusRepository.deleteAllByChatRoomIdIn(chatRoomIds); | ||
| chatParticipantRepository.deleteAllByChatRoomIdIn(chatRoomIds); | ||
| chatRoomRepository.deleteAllById(chatRoomIds); |
There was a problem hiding this comment.
1:1 채팅방을 통째로 삭제하면, 탈퇴하지 않은 상대방 입장에서는 아무 통보 없이 대화 기록이 사라지게 됩니다!
참여자 한 명이 빠지면 방을 유지하기 어렵다는 제약 자체는 이해되지만, 이게 기획 쪽과 합의된 정책인지 확인이 필요해 보입니다. 필요하면 상대방 메시지는 보존하고 탈퇴한 참여자 정보만 "알 수 없음" 등으로 치환하는 방식도 고려해볼 수 있을 것 같습니다.
| List<Long> chatParticipantIds = chatParticipantRepository.findAllIdsBySiteUserId(siteUserId); | ||
| chatReadStatusRepository.deleteAllByChatParticipantIdIn(chatParticipantIds); | ||
| chatParticipantRepository.deleteAllBySiteUserId(siteUserId); | ||
| reportRepository.deleteAllByReporterId(siteUserId); |
There was a problem hiding this comment.
deleteAllByReporterId는 이 사용자가 "신고한" 기록만 지우는데, 이 사용자가 "신고당한" 기록(report.reported_id = siteUserId)이나 이 사용자의 게시글/채팅메시지를 대상(target_id)으로 한 신고 기록은 그대로 남아 고아 데이터가 발생할 수 있을 것 같습니다!
target_id/reported_id에는 FK가 없어서(ReportRepository, V25__create_report_table.sql 확인) 삭제 자체가 실패하지는 않지만, 대상이 사라진 report 행이 고아 데이터로 계속 쌓이게 됩니다. 이번 PR 범위 밖일 수 있지만 후속으로 reportedId/targetId 기준 정리도 필요해 보입니다.
관련 이슈
작업 내용
탈퇴 후 30일이 지난 사용자를 삭제하는
UserRemovalScheduler가 외래키 제약으로 인해 매일 밤 롤백되고 있었습니다.채팅
메시지 → 읽음 상태 → 참여자(양쪽) → 방 순으로 정리합니다.
멘토링
뉴스
게시글·채팅 메시지의 소프트 삭제
is_deleted = true) 게시글과 메시지는 엔티티 조회 필터에 걸려삭제되지 않았습니다. 플래그를 되돌린 뒤 삭제해 JPA cascade가 그대로 동작하게 했습니다.
정지 기록
이를 위해
user_ban.banned_by를 nullable로 변경했습니다. (V60)프로필 이미지
S3Service.deleteExProfile이 빈 key로 S3를 호출해 예외를 던지던 문제를 막았습니다.profile_image_url이 null이거나 빈 문자열인 계정에서 발생합니다.특이 사항
리뷰 요구사항 (선택)